﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Emu8080.Parser;
using Antlr.Runtime.Tree;
using Emu8080.Compiler.Structures;
using Emu8080.Parser.GeneratedParser;

namespace Emu8080.Compiler
{
    public delegate void MessageHandler(int line, int pos, string message);

    public class Compiler
    {
        CommonTree parsedTree;
        Program program;
        string[] sourceCodeLines;

        public event MessageHandler OnError;

        /*public Compiler(TextReader tr)
        {
            parsedTree = AsmParser.Parse(tr);
            program = new Program();
            var a = tr.ReadLine();
        }*/

        public Compiler(string sourceCode)
        {
            var sr = new StringReader(sourceCode);
            parsedTree = AsmParser.Parse(sr);
            program = new Program();

            sourceCodeLines = sourceCode.Split('\n');
            for (int i = 0; i < sourceCodeLines.Length; i++)
            {
                sourceCodeLines[i] = sourceCodeLines[i].TrimEnd();
            }
        }

        public Program Compile()
        {
            PreProcessLabels();

            var pu = program.Module.ProcessingUnits;
            Label openedLabel = null;
            ProcessingUnit generated = null;
            foreach (CommonTree cmd in parsedTree.Children)
            {
                try
                {
                    switch (cmd.Type)
                    {
                        case AsmI8080Parser.T_LABEL:
                            {
                                openedLabel = MakeLabel(cmd);
                                pu.Add(openedLabel);
                                break;
                            }
                        case AsmI8080Parser.T_CMD:
                            {
                                generated = MakeCompilerCommand(cmd);
                                if (openedLabel != null)
                                {
                                    openedLabel.Destanation = generated as IProcessingUnitFundamental;
                                    program.Module.Labels[openedLabel.Name] = generated as IProcessingUnitFundamental;

                                    openedLabel = null;
                                }
                                pu.Add(generated);
                                break;
                            }
                        case AsmI8080Parser.T_OP_0:
                        case AsmI8080Parser.T_OP_IM16:
                        case AsmI8080Parser.T_OP_RP_IM16:
                        case AsmI8080Parser.T_OP_IM8:
                        case AsmI8080Parser.T_OP_REG_IM8:
                        case AsmI8080Parser.T_OP_REG_REG:
                        case AsmI8080Parser.T_OP_REG:
                        case AsmI8080Parser.T_OP_RP:
                        case AsmI8080Parser.T_OP_RST:
                            {
                                generated = MakeInstruction(cmd);
                                if (openedLabel != null)
                                {
                                    openedLabel.Destanation = generated as IProcessingUnitFundamental;
                                    program.Module.Labels[openedLabel.Name] = generated as IProcessingUnitFundamental;

                                    openedLabel = null;
                                }
                                pu.Add(generated);
                                break;
                            }
                        case AsmI8080Parser.T_COMMENT:
                        default:
                            break;
                    }
                }
                catch
                { }
            }

            MakeAdresses();
            ProcessLabelRefs();

            // fill program start adress if presented
            if (pu.Last() is CompilerCommandEnd)
            {
                var cmdEnd = pu.Last() as CompilerCommandEnd;
                if (cmdEnd.Address != null)
                    program.StartAddress = (ushort)(Int16)cmdEnd.Address.Value;
                else
                    SetStartAddressToFirstData();
            }
            else
            {
                if (OnError != null)
                    OnError(sourceCodeLines.Length+1, 0, ".end command was not found at the program end. Check sources.");
                SetStartAddressToFirstData();
            }

            return program;
        }

        private void SetStartAddressToFirstData()
        {
            var pu = program.Module.ProcessingUnits;
            foreach (var item in pu)
            {
                if (item is IProcessingUnitFundamental)
                {
                    program.StartAddress = (item as IProcessingUnitFundamental).Address;
                    break;
                }
            }
        }

        void PreProcessLabels()
        {
            var mod = program.Module;
            foreach (CommonTree cmd in parsedTree.Children)
            {
                if (cmd.Type == AsmI8080Parser.T_LABEL)
                {
                    var labelNode = cmd.GetChild(0);
                    mod.Labels.Add(labelNode.Text, null);
                }
            }
        }

        void MakeAdresses()
        {
            ushort currentAddress = 0x800;
            foreach (var item in program.Module.ProcessingUnits)
            {
                if (item is CompilerCommandOrg)
                {
                    currentAddress = (item as CompilerCommandOrg).Address;
                }
                else if (item is Instruction)
                {
                    var inst = item as Instruction;
                    inst.Address = currentAddress;
                    currentAddress += (ushort)inst.Size;
                }
                else if (item is CompilerCommandDb)
                {
                    var cmd = item as CompilerCommandDb;
                    cmd.Address = currentAddress;
                    currentAddress += (ushort)cmd.Operand.Size;
                }
                else if (item is CompilerCommandDw)
                {
                    var cmd = item as CompilerCommandDw;
                    cmd.Address = currentAddress;
                    currentAddress += (ushort)cmd.Operand.Length;
                }
                
            }
        }

        void ProcessLabelRefs()
        {
            foreach (var item in program.Module.ProcessingUnits)
            {
                try
                {
                    if (item is InstructionIm16)
                    {
                        var inst = item as InstructionIm16;
                        inst.Operand = ConvertLabelRefToAdress(inst.Operand);
                    }
                    else if (item is InstructionRpIm16)
                    {
                        var inst = item as InstructionRpIm16;
                        inst.Operand2 = ConvertLabelRefToAdress(inst.Operand2);
                    }
                    else if (item is CompilerCommandEnd)
                    {
                        var cmd = item as CompilerCommandEnd;
                        cmd.Address = ConvertLabelRefToAdress(cmd.Address);
                    }
                }
                catch
                { }
            }
        }

        ImmediateOperand16bit ConvertLabelRefToAdress(ImmediateOperand16bit src)
        {
            if (src == null)
                return src;

            if (src.Type == ImmediateOperand16bit.ImmediateOperand16bitType.Label)
            {
                IProcessingUnitFundamental inst;
                if (program.Module.Labels.TryGetValue(src.Value as string, out inst))
                {
                    if (inst == null)
                        throw ThrowError(0, 0, "Internal error: label doesn't reference an instruction");

                    return new ImmediateOperand16bit((short)inst.Address);
                }
                else
                    throw ThrowError(0, 0, "Internal error: label not found");
            }
            else if (src.Type == ImmediateOperand16bit.ImmediateOperand16bitType.Instruction)
            {
                if (src.Value != null)
                {
                    var inst = src.Value as IProcessingUnitFundamental;
                    return new ImmediateOperand16bit((short)inst.Address);
                }
                else
                    throw ThrowError(0, 0, "Internal error: label doesn't reference an instruction");
            }
            else if (src.Type == ImmediateOperand16bit.ImmediateOperand16bitType.Number)
                return src;
            else
                throw ThrowError(0, 0, "Internal error: wrong ImmediateOperand16bit type");

        }

        #region Instruction maker

        Dictionary<int, Operators> operatorsCorrespondingTable = new Dictionary<int, Operators>()
        {
            { AsmI8080Parser.OP_NOP  , Operators.NOP	},
            { AsmI8080Parser.OP_HLT  , Operators.HLT    },
            { AsmI8080Parser.OP_EI   , Operators.EI     },
            { AsmI8080Parser.OP_DI   , Operators.DI     },
            { AsmI8080Parser.OP_SPHL , Operators.SPHL   },
            { AsmI8080Parser.OP_XCHG , Operators.XCHG   },
            { AsmI8080Parser.OP_XTHL , Operators.XTHL   },
            { AsmI8080Parser.OP_DAA  , Operators.DAA    },
            { AsmI8080Parser.OP_CMA  , Operators.CMA    },
            { AsmI8080Parser.OP_STC  , Operators.STC    },
            { AsmI8080Parser.OP_CMC  , Operators.CMC    },
            { AsmI8080Parser.OP_RLC  , Operators.RLC    },
            { AsmI8080Parser.OP_RRC  , Operators.RRC    },
            { AsmI8080Parser.OP_RAL  , Operators.RAL    },
            { AsmI8080Parser.OP_RAR  , Operators.RAR    },
            { AsmI8080Parser.OP_PCHL , Operators.PCHL   },
            { AsmI8080Parser.OP_RET  , Operators.RET    },
            { AsmI8080Parser.OP_RNZ  , Operators.RNZ    },
            { AsmI8080Parser.OP_RZ   , Operators.RZ     },
            { AsmI8080Parser.OP_RNC  , Operators.RNC    },
            { AsmI8080Parser.OP_RC   , Operators.RC     },
            { AsmI8080Parser.OP_RPO  , Operators.RPO    },
            { AsmI8080Parser.OP_RPE  , Operators.RPE    },
            { AsmI8080Parser.OP_RP   , Operators.RP     },
            { AsmI8080Parser.OP_RM   , Operators.RM     },

            { AsmI8080Parser.OP_LDA  , Operators.LDA    },
            { AsmI8080Parser.OP_STA  , Operators.STA    },
            { AsmI8080Parser.OP_LHLD , Operators.LHLD   },
            { AsmI8080Parser.OP_SHLD , Operators.SHLD   },
            { AsmI8080Parser.OP_JMP  , Operators.JMP    },
            { AsmI8080Parser.OP_JNZ  , Operators.JNZ    },
            { AsmI8080Parser.OP_JZ   , Operators.JZ     },
            { AsmI8080Parser.OP_JNC  , Operators.JNC    },
            { AsmI8080Parser.OP_JC   , Operators.JC     },
            { AsmI8080Parser.OP_JPO  , Operators.JPO    },
            { AsmI8080Parser.OP_JPE  , Operators.JPE    },
            { AsmI8080Parser.OP_JP   , Operators.JP     },
            { AsmI8080Parser.OP_JM   , Operators.JM     },
            { AsmI8080Parser.OP_CALL , Operators.CALL   },
            { AsmI8080Parser.OP_CNZ  , Operators.CNZ    },
            { AsmI8080Parser.OP_CZ   , Operators.CZ     },
            { AsmI8080Parser.OP_CNC  , Operators.CNC    },
            { AsmI8080Parser.OP_CC   , Operators.CC     },
            { AsmI8080Parser.OP_CPO  , Operators.CPO    },
            { AsmI8080Parser.OP_CPE  , Operators.CPE    },
            { AsmI8080Parser.OP_CP   , Operators.CP     },
            { AsmI8080Parser.OP_CM   , Operators.CM     },

            { AsmI8080Parser.OP_LXI  , Operators.LXI    },

            { AsmI8080Parser.OP_ADI  , Operators.ADI    },
            { AsmI8080Parser.OP_ACI  , Operators.ACI    },
            { AsmI8080Parser.OP_SUI  , Operators.SUI    },
            { AsmI8080Parser.OP_SBI  , Operators.SBI    },
            { AsmI8080Parser.OP_ANI  , Operators.ANI    },
            { AsmI8080Parser.OP_XRI  , Operators.XRI    },
            { AsmI8080Parser.OP_ORI  , Operators.ORI    },
            { AsmI8080Parser.OP_CPI  , Operators.CPI    },
            { AsmI8080Parser.OP_IN   , Operators.IN     },
            { AsmI8080Parser.OP_OUT  , Operators.OUT    },

            { AsmI8080Parser.OP_MVI  , Operators.MVI    },

            { AsmI8080Parser.OP_MOV  , Operators.MOV    },

            { AsmI8080Parser.OP_ADD  , Operators.ADD    },
            { AsmI8080Parser.OP_ADC  , Operators.ADC    },
            { AsmI8080Parser.OP_SUB  , Operators.SUB    },
            { AsmI8080Parser.OP_SBB  , Operators.SBB    },
            { AsmI8080Parser.OP_ANA  , Operators.ANA    },
            { AsmI8080Parser.OP_XRA  , Operators.XRA    },
            { AsmI8080Parser.OP_ORA  , Operators.ORA    },
            { AsmI8080Parser.OP_CMP  , Operators.CMP    },
            { AsmI8080Parser.OP_INR  , Operators.INR    },
            { AsmI8080Parser.OP_DCR  , Operators.DCR    },

            { AsmI8080Parser.OP_LDAX , Operators.LDAX   },
            { AsmI8080Parser.OP_STAX , Operators.STAX   },
            { AsmI8080Parser.OP_DAD  , Operators.DAD    },
            { AsmI8080Parser.OP_INX  , Operators.INX    },
            { AsmI8080Parser.OP_DCX  , Operators.DCX    },
            { AsmI8080Parser.OP_PUSH , Operators.PUSH   },
            { AsmI8080Parser.OP_POP  , Operators.POP    }
        };
        Instruction MakeInstruction(CommonTree t)
        {
            if (t.ChildCount != 1)
                throw ThrowError(t.Line, t.CharPositionInLine, "Instruction compilation failed: instruction node has more than one operator");

            var oper = t.Children[0];
            Operators @operator;
            if (!operatorsCorrespondingTable.TryGetValue(oper.Type, out @operator))
                throw ThrowError(t.Line, t.CharPositionInLine, "Instruction compilation failed: unrecognized operator");

            Instruction inst;
            switch (t.Type)
            {
                case AsmI8080Parser.T_OP_0:
                    inst = new Instruction()
                    {
                        Operator = @operator
                    };
                    break;
                case AsmI8080Parser.T_OP_IM16:
                    {
                        if (oper.ChildCount != 1)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 1 operand", oper.Text));

                        var astOperand1 = oper.GetChild(0);

                        try
                        {
                            ImmediateOperand16bit operand = ParseIm16(astOperand1);

                            inst = new InstructionIm16()
                            {
                                Operator = @operator,
                                Operand = operand
                            };
                        }
                        catch(Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operand.", oper.Text));
                        }

                        break;
                    }
                case AsmI8080Parser.T_OP_RP_IM16:
                    {
                        if (oper.ChildCount != 2)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 2 operands", oper.Text));

                        var astOperand1 = oper.GetChild(0);
                        var astOperand2 = oper.GetChild(1);


                        try
                        {
                            RegisterPairs operand1 = ParseRp(astOperand1);
                            ImmediateOperand16bit operand2 = ParseIm16(astOperand2);

                            inst = new InstructionRpIm16()
                            {
                                Operator = @operator,
                                Operand1 = operand1,
                                Operand2 = operand2
                            };
                        }
                        catch (Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operand.", oper.Text));
                        }

                        break;
                    }
                case AsmI8080Parser.T_OP_IM8:
                    {
                        if (oper.ChildCount != 1)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 1 operand", oper.Text));

                        var astOperand1 = oper.GetChild(0);

                        try
                        {
                            SByte operand = ParseIm8(astOperand1);

                            inst = new InstructionIm8()
                            {
                                Operator = @operator,
                                Operand = (Byte)operand
                            };
                        }
                        catch (Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operand.", oper.Text));
                        }

                        break;
                    }
                case AsmI8080Parser.T_OP_REG_IM8:
                    {
                        if (oper.ChildCount != 2)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 2 operands", oper.Text));

                        var astOperand1 = oper.GetChild(0);
                        var astOperand2 = oper.GetChild(1);
                        
                        try
                        {
                            Registers operand1 = ParseReg(astOperand1);
                            SByte operand2 = ParseIm8(astOperand2);
                            inst = new InstructionRegIm8()
                            {
                                Operator = @operator,
                                Operand1 = operand1,
                                Operand2 = (Byte)operand2
                            };
                        }
                        catch (Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operands.", oper.Text));
                        }
                        
                        break;
                    }
                case AsmI8080Parser.T_OP_REG_REG:
                    {
                        if (oper.ChildCount != 2)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 2 operands", oper.Text));

                        var astOperand1 = oper.GetChild(0);
                        var astOperand2 = oper.GetChild(1);

                        try
                        {
                            Registers operand1 = ParseReg(astOperand1);
                            Registers operand2 = ParseReg(astOperand2);
                            
                            inst = new InstructionRegReg()
                            {
                                Operator = @operator,
                                Operand1 = operand1,
                                Operand2 = operand2
                            };
                        }
                        catch (Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operands.", oper.Text));
                        }
                        
                        break;
                    }
                case AsmI8080Parser.T_OP_REG:
                    {
                        if (oper.ChildCount != 1)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 1 operand", oper.Text));

                        var astOperand1 = oper.GetChild(0);

                        try
                        {
                            Registers operand1 = ParseReg(astOperand1);

                            inst = new InstructionReg()
                            {
                                Operator = @operator,
                                Operand = operand1,
                            };
                        }
                        catch (Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operand.", oper.Text));
                        }

                        break;
                    }
                case AsmI8080Parser.T_OP_RP:
                    {
                        if (oper.ChildCount != 1)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 1 operand", oper.Text));

                        var astOperand1 = oper.GetChild(0);

                        try
                        {
                            RegisterPairs operand = ParseRp(astOperand1);

                            inst = new InstructionRp()
                            {
                                Operator = @operator,
                                Operand = operand,
                            };
                        }
                        catch (Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operand.", oper.Text));
                        }

                        break;
                    }
                case AsmI8080Parser.T_OP_RST:
                    {
                        if (oper.ChildCount != 1)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0} should have 1 operand", oper.Text));

                        var astOperand1 = oper.GetChild(0);

                        int operand;
                        try
                        {
                            operand = ParseInteger(astOperand1);
                        }
                        catch (Exception)
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: instruction {0}. Check operand.", oper.Text));
                        }
                        
                        if (operand < 0 || operand >= 8)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("Instruction compilation failed: operand of instruction {0} should be an integer between 0 and 7", oper.Text));

                        inst = new InstructionRst()
                        {
                            Operator = @operator,
                            Operand = operand,
                        };
                        break;
                    }
                default:
                    throw ThrowError(t.Line, t.CharPositionInLine, "Instruction compilation failed: unrecognized category of operator");

            }
            inst.Meta.LineNumber = t.Line;
            if (t.Line >= 1 && t.Line <= sourceCodeLines.Length)
                inst.Meta.LineContent = sourceCodeLines[t.Line - 1];
            return inst;
        }
        
        #endregion
        
        #region Helpful parsers
        
        Dictionary<string, Registers> registersCorrespondingTable = new Dictionary<string, Registers>()
        {
            { "b" , Registers.B },
            { "c" , Registers.C },
            { "d" , Registers.D },
            { "e" , Registers.E },
            { "h" , Registers.H },
            { "l" , Registers.L },
            { "m" , Registers.M },
            { "a" , Registers.A }
        };
        Registers ParseReg(ITree t)
        {
            if (t.Type != AsmI8080Parser.T_REF_LABEL || t.ChildCount != 1)  // TODO надо сделать правило для регистров, но пока можно парсить через T_REF_LABEL
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed. Operand should be a valid register");

            Registers operand;
            if (!registersCorrespondingTable.TryGetValue(t.GetChild(0).Text.ToLower(), out operand))
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed. Operand should be a valid register");

            return operand;
        }

        Dictionary<string, RegisterPairs> registerPairsCorrespondingTable = new Dictionary<string, RegisterPairs>()
        {
            { "bc" , RegisterPairs.BC },
            { "de" , RegisterPairs.DE },
            { "hl" , RegisterPairs.HL },
            { "sp" , RegisterPairs.SP },
            { "psw" , RegisterPairs.PSW },
            { "a" , RegisterPairs.A }
        };
        RegisterPairs ParseRp(ITree t)
        {
            if (t.Type != AsmI8080Parser.T_REF_LABEL || t.ChildCount != 1)  // TODO надо сделать правило для регистров, но пока можно парсить через T_REF_LABEL
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed. Operand should be a valid register");

            RegisterPairs operand;
            if (!registerPairsCorrespondingTable.TryGetValue(t.GetChild(0).Text.ToLower(), out operand))
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed. Operand should be a valid register pair");

            return operand;
        }

        ImmediateOperand16bit ParseIm16(ITree t)
        {
            if (t.ChildCount != 1)
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed.");

            if (t.Type == AsmI8080Parser.T_INT)
            {
                int intValue;
                try
                {
                    var integerNode = t.GetChild(0);
                    intValue = ParseInteger(integerNode);
                }
                catch (Exception)
                {
                    throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed: operand should be a valid integer");
                }

                if (intValue > UInt16.MaxValue || intValue < Int16.MinValue)
                    throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed: operand is out of bound 16 bit integer");

                Int16 int16Value = (Int16)intValue;

                var result = new ImmediateOperand16bit(int16Value);
                return result;
            }
            else if (t.Type == AsmI8080Parser.T_REF_LABEL)
            {
                var labelNode = t.GetChild(0);
                var labelName = labelNode.Text;

                var mod = program.Module;

                if (!mod.Labels.ContainsKey(labelName))
                    throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed: label not found");

                var instruction = mod.Labels[labelName];

                ImmediateOperand16bit result;
                if (instruction != null)
                    result = new ImmediateOperand16bit(instruction);
                else
                    result = new ImmediateOperand16bit(labelName);

                return result;
            }
            else
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed: operand should be an integer or label");
        }

        SByte ParseIm8(ITree t)
        {
            if (t.ChildCount != 1)
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed.");

            if (t.Type == AsmI8080Parser.T_INT)
            {
                int intValue;
                try
                {
                    var integerNode = t.GetChild(0);
                    intValue = ParseInteger(integerNode);
                }
                catch (Exception)
                {
                    throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed: operand should be a valid integer");
                }

                if (intValue > Byte.MaxValue || intValue < SByte.MinValue)
                    throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed: operand is out of bound 8 bit integer");

                SByte int8Value = (SByte)intValue;

                return int8Value;
            }
            else
                throw ThrowError(t.Line, t.CharPositionInLine, "Operand parsing failed: operand should be an integer or label");
        }


        int ParseInteger(ITree t)
        {
            if (t.Type == AsmI8080Parser.INT)
            {
                try
                {
                    return int.Parse(t.Text);
                }
                catch (Exception)
                {
                    throw ThrowError(t.Line, t.CharPositionInLine, "Parsing integer failed");
                }
            }
            else if (t.Type == AsmI8080Parser.HEX)
            {
                try
                {
                    var numberstr = t.Text.Substring(0, t.Text.Length - 1);
                    return Convert.ToInt32(numberstr, 16);
                }
                catch (Exception)
                {
                    throw ThrowError(t.Line, t.CharPositionInLine, "Parsing integer failed");
                }
            }
            else
                throw ThrowError(t.Line, t.CharPositionInLine, "Parsing integer failed");

        }

        #endregion

        #region Label maker

        Label MakeLabel(CommonTree t)
        {
            var labelNode = t.GetChild(0);
            var label = new Label(labelNode.Text);
            label.Meta.LineNumber = t.Line;
            if (t.Line >= 1 && t.Line <= sourceCodeLines.Length)
                label.Meta.LineContent = sourceCodeLines[t.Line - 1];
            return label;
        }

        #endregion

        #region Compiler command maker

        Dictionary<int, CompilerCommands> commandsCorrespondingTable = new Dictionary<int, CompilerCommands>()
        {
            { AsmI8080Parser.CMD_ORG , CompilerCommands.ORG	 },
            { AsmI8080Parser.CMD_DB  , CompilerCommands.DB   },
            { AsmI8080Parser.CMD_DW  , CompilerCommands.DW   },
            { AsmI8080Parser.CMD_END , CompilerCommands.END  }
        };
        CompilerCommand MakeCompilerCommand(CommonTree t)
        {
            if (t.ChildCount != 1)
                throw ThrowError(t.Line, t.CharPositionInLine, "command processing failed: command node has more than one operator");

            var cmdNode = t.GetChild(0);

            CompilerCommands cmdEnum;
            if (!commandsCorrespondingTable.TryGetValue(cmdNode.Type, out cmdEnum))
                throw ThrowError(t.Line, t.CharPositionInLine, "command processing failed: unrecognized command");

            CompilerCommand cc;
            switch (cmdEnum)
            {
                case CompilerCommands.ORG:
                    {
                        if (cmdNode.ChildCount != 1)
                            throw ThrowError(cmdNode.Line, cmdNode.CharPositionInLine, string.Format("command processing failed: command {0} should have 1 operand", cmdNode.Text));

                        var operandNode = cmdNode.GetChild(0);

                        int operandInt;
                        try
                        {
                            operandInt = ParseInteger(operandNode);
                        }
                        catch
                        {
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("command processing failed: command {0}. Check operand.", cmdNode.Text));
                        }

                        if (operandInt < 0 || operandInt > 0xFFFF)
                            throw ThrowError(t.Line, t.CharPositionInLine, string.Format("command processing failed: command {0}. Operand is out of bound 16-bit address space", cmdNode.Text));

                        cc = new CompilerCommandOrg()
                        {
                            Address = (ushort)operandInt
                        };

                        break;
                    }
                case CompilerCommands.DB:
                    {
                        if (cmdNode.ChildCount == 0)
                            throw ThrowError(cmdNode.Line, cmdNode.CharPositionInLine, string.Format("command processing failed: command {0} should have 1 or more operand(s)", cmdNode.Text));

                        CommandDbOperand operandIm = null;
                        if (false/*cmdNode.GetChild(0).Type == AsmI8080Parser.STRING*/)
                        {
                            /*var operandNode = cmdNode.GetChild(0);

                            try
                            {
                                operandIm = ParseIm16(operandNode);
                            }
                            catch
                            {
                                throw ThrowError(t.Line, t.CharPositionInLine, string.Format("command processing failed: command {0}. Check operand.", cmdNode.Text));
                            }*/
                        }
                        else
                        {
                            var bytes = new byte [cmdNode.ChildCount];

                            for (int i = 0; i < cmdNode.ChildCount; i++)
			                {
                                var opNode = cmdNode.GetChild(i);
                                try
                                {
                                    bytes[i] = (byte)ParseInteger(opNode);
                                }
                                catch
                                {
                                    throw ThrowError(cmdNode.Line, cmdNode.CharPositionInLine, string.Format("command processing failed: command {0} has one or more wrong operands", cmdNode.Text));
                                }
			                }

                            operandIm = new CommandDbOperand(bytes);
                        }

                        cc = new CompilerCommandDb()
                        {
                            Operand = operandIm
                        };

                        break;
                    }
                case CompilerCommands.DW:
                    {
                        if (cmdNode.ChildCount == 0)
                            throw ThrowError(cmdNode.Line, cmdNode.CharPositionInLine, string.Format("command processing failed: command {0} should have 1 or more operand(s)", cmdNode.Text));

                        var words = new ushort[cmdNode.ChildCount];

                        for (int i = 0; i < cmdNode.ChildCount; i++)
                        {
                            var opNode = cmdNode.GetChild(i);
                            try
                            {
                                words[i] = (ushort)ParseInteger(opNode);
                            }
                            catch
                            {
                                throw ThrowError(cmdNode.Line, cmdNode.CharPositionInLine, string.Format("command processing failed: command {0} has one or more wrong operands", cmdNode.Text));
                            }
                        }

                        cc = new CompilerCommandDw()
                        {
                            Operand = words
                        };

                        break;
                    }
                case CompilerCommands.END:
                    {
                        if (cmdNode.ChildCount > 1)
                            throw ThrowError(cmdNode.Line, cmdNode.CharPositionInLine, string.Format("command processing failed: command {0} should have 1 operand or have no one", cmdNode.Text));

                        ImmediateOperand16bit operandIm = null;
                        if (cmdNode.ChildCount == 1)
                        {
                            var operandNode = cmdNode.GetChild(0);

                            try
                            {
                                operandIm = ParseIm16(operandNode);
                            }
                            catch
                            {
                                throw ThrowError(t.Line, t.CharPositionInLine, string.Format("command processing failed: command {0}. Check operand.", cmdNode.Text));
                            }
                        }

                        cc = new CompilerCommandEnd()
                        {
                            Address = operandIm
                        };

                        break;
                    }
                default:
                    throw ThrowError(t.Line, t.CharPositionInLine, "Instruction compilation failed: unrecognized category of operator");
            }
            cc.Meta.LineNumber = t.Line;
            if (t.Line >= 1 && t.Line <= sourceCodeLines.Length)
                cc.Meta.LineContent = sourceCodeLines[t.Line - 1];
            return cc;
        }

        #endregion

        private Exception ThrowError(int line, int pos, string message)
        {
            if (OnError != null)
                OnError(line, pos, message);
            return new Exception();
        }
    }
}
